package top.huxy.boot.aspect;

import com.alibaba.fastjson.JSON;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import top.huxy.boot.annotation.NoRecordRequest;
import top.huxy.boot.annotation.Operation;
import top.huxy.boot.annotation.RequestRemake;
import top.huxy.boot.config.RedisKeyConfig;
import top.huxy.boot.model.entity.VisitLog;
import top.huxy.boot.util.IPAddressUtils;
import top.huxy.boot.util.RequestUtils;
import top.huxy.boot.util.StringUtils;
import top.huxy.boot.util.obj.ThreadLocalRequestDetail;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * @Classname VisitLogAspect
 * @Description 监控所有控制器的访问，记录请求信息、执行状态
 * @Date 2021/9/17 12:57
 * @Created by huxy
 * 2021.9.24不再使用这个方案监控控制器了，因为有些会异常的控制台不会执行，比如缺少请求参数
 * 2021.10.20重新启用这个方案监控控制器
 * @see ThreadLocalRequestDetail
 */
@Aspect
@Component
public class VisitLogAspect {

    @Autowired
    RedisTemplate<String, Object> redisTemplate;

    //监控所有控制器
    @Pointcut("execution(* top.huxy.boot.controller..*.*(..))")
    public void visitPointcut() {
    }

    @Around("visitPointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = RequestUtils.getCurrentRequest();
        //获取执行的方法信息

        String className = joinPoint.getTarget().getClass().getName();
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();

        //如果方法要求不记录请求，则不记录
        if (method.getAnnotation(NoRecordRequest.class) != null) {
            return joinPoint.proceed();
        }

        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();
        String methodHandler = StringUtils.merge(className, ".", methodName, "(", StringUtils.getSimpleClassName(parameterTypes), ")");

        //http请求方法，get,post等
        String requestMethod = request.getMethod();

        //获取请求的所有参数信息
        String requestParams = null;

        //请求ip
        String ip = IPAddressUtils.getIpAddress(request);

        //ip对应的城市来源信息
        String cityInfo = IPAddressUtils.getCityInfo(ip);

        //重装系统和浏览器等信息
        Map<String, String> osAndBrowser = RequestUtils.parseOsAndBrowser(request);

        //uri信息
        String requestURI = request.getRequestURI();

        //生成请求ID
        String requestID = RequestUtils.getRequestID(request);

        //执行状态，成功还是有抛出异常
        boolean state = true;

        String remake = null;

        RequestRemake requestRemake = method.getAnnotation(RequestRemake.class);
        if (requestRemake != null) {
            remake = StringUtils.or(requestRemake.value(), null);
            if (requestRemake.saveParams()) {
                requestParams = JSON.toJSONString(request.getParameterMap());
            }
        } else {
            requestParams = JSON.toJSONString(request.getParameterMap());
            Operation operation = method.getAnnotation(Operation.class);
            if (operation != null) {
                remake = StringUtils.or(operation.value(), null);
            }
        }

        //方法结果
        Object result;

        //目标方法开始执行时间
        long time_start = System.currentTimeMillis();

        try {
            //执行目标方法
            result = joinPoint.proceed();
        } catch (Throwable th) {
            //抛出异常，改变状态为false
            state = false;
            throw th;
        } finally {
            //方法执行完后的时间
            long time_end = System.currentTimeMillis();

            //计算两时间差
            long time = time_end - time_start;

            //生成日志对象
            VisitLog visitLog = new VisitLog(requestID,
                    methodHandler,
                    requestMethod,
                    requestURI,
                    requestParams,
                    ip,
                    cityInfo,
                    osAndBrowser.get("os"),
                    osAndBrowser.get("browser"),
                    time,
                    state,
                    time_end,
                    remake
            );

            //将日志记录到缓存
            redisTemplate.opsForList().rightPush(RedisKeyConfig.TEMP_VISITOR_LOG, visitLog);
        }

        //返回方法执行结果
        return result;
    }
}
